package work.jame.blog.configuration.filter;

import org.jetbrains.annotations.NotNull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import work.jame.blog.configuration.security.LoginUser;
import work.jame.blog.configuration.spring.SpringConfig;
import work.jame.blog.framework.limit.RequestLimit;
import work.jame.blog.framework.limit.AbstractRequestLimitHandler;
import work.jame.blog.framework.result.Result;
import work.jame.blog.framework.utils.http.IpUtils;
import work.jame.blog.framework.utils.http.WebUtils;
import work.jame.blog.framework.utils.redis.RedisService;
import work.jame.blog.framework.utils.redis.RedisUtils;
import work.jame.blog.framework.utils.security.SpringSecurityUtils;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.Method;
import java.util.*;
import java.util.concurrent.TimeUnit;

/**
 * 请求限流拦截器
 * 使用redis的过期时间来实现
 * 固定窗口?
 * @author Jame!
 * @date 2023/5/11 下午 10:00
 */

public class RequestLimitInterceptor implements HandlerInterceptor {

    private static final Logger logger = LoggerFactory.getLogger(RequestLimitInterceptor.class);

    //拦截器在springContext创建之前完成,无法通过spring的ioc进行注入
    private RedisService redisService;

    //自定义限流策略缓存map
    private final HashMap<Class<? extends AbstractRequestLimitHandler>, AbstractRequestLimitHandler> requestLimitHandlerCacheMap = new HashMap<>(8);

    //全局请求限流策略
    private static final List<AbstractRequestLimitHandler> GLOBAL_REQUEST_LIMIT_HANDLERS = new ArrayList<>(8);


    /**
     * 请求前置处理 限流
     *
     * @param request  请求
     * @param response 响应
     * @param handler  要执行的处理器
     * @return 是否放行
     */
    @Override
    public boolean preHandle(@NotNull HttpServletRequest request,
                             @NotNull HttpServletResponse response,
                             @NotNull Object handler) {

        //先执行全局请求限流
        for (AbstractRequestLimitHandler limitHandler : GLOBAL_REQUEST_LIMIT_HANDLERS) {
            AbstractRequestLimitHandler.Operation operation = limitHandler.handler(request, response);
            switch (operation) {
                case PROHIBIT:
                    return false;
                case SKIP:
                    return true;
                case ALLOW:
                    //do noting
                    break;
            }
        }

        boolean isHandlerMethod = handler instanceof HandlerMethod;
        if (!isHandlerMethod) {
            return true;
        }
        HandlerMethod handlerMethod = (HandlerMethod) handler;
        Method method = handlerMethod.getMethod();
        //如果当前请求没有设置限流,则直接放行
        if (!method.isAnnotationPresent(RequestLimit.class)) {
            return true;
        }

        if (Objects.isNull(redisService)) {
            redisService = SpringConfig.getBean(RedisService.class);
        }

        String requestURI = request.getRequestURI();
        RequestLimit requestLimit = method.getAnnotation(RequestLimit.class);
        //获取时间段内最大请求次数
        int maxRequestNumber = requestLimit.value();
        //获取限流模式
        String mode = requestLimit.mode();
        //时间单元
        TimeUnit timeUnit = requestLimit.timeUnit();

        //用户限流模式
        String userLimitMode = null;
        //ip限流模式
        String ipLimitMode = null;

        switch (mode) {
            case "user_ip":
                String[] modes = mode.split("_");
                userLimitMode = modes[0];
                ipLimitMode = modes[1];
                break;
            case "user":
                userLimitMode = mode;
                break;
            case "ip":
                ipLimitMode = mode;
                break;
            default:
                logger.error("请求路径:" + requestURI + "的限流模式未找到:" + mode + ",可用值: user  ip  user_ip ");
                throw new RuntimeException("限流模式未找到");
        }
        //使用用户模式限流
        if (!Objects.isNull(userLimitMode)) {
            LoginUser loginUser = SpringSecurityUtils.getCurrentLoginUserInfo();
            Long userId = loginUser.getUser().getId();
            String userModeKey = RedisUtils.getRequestLimitUserModeKey(userId, requestURI);
            //这里成功也继续执行,因为还有ip限流模式没有检查
            if (requestLimitCheck(userModeKey, maxRequestNumber, timeUnit, response)) {
                return false;
            }
        }
        //使用ip模式限流
        if (!Objects.isNull(ipLimitMode)) {
            String ip = IpUtils.getIpAddr(request);
            String ipModeKey = RedisUtils.getRequestLimitIpModeKey(ip, requestURI);
            //这里成功也继续执行,继续检查是否存在自定义限流策略
            if (requestLimitCheck(ipModeKey, maxRequestNumber, timeUnit, response)) {
                return false;
            }
        }
        Class<? extends AbstractRequestLimitHandler>[] requestLimitHandlers = requestLimit.handlers();
        //如果没有设置自定义限流策略则放行
        if (requestLimitHandlers.length == 1 && requestLimitHandlers[0] == AbstractRequestLimitHandler.class) {
            return true;
        }

        for (Class<? extends AbstractRequestLimitHandler> requestLimitHandler : requestLimitHandlers) {
            if (!requestLimitHandlerCacheMap.containsKey(requestLimitHandler)) {
                AbstractRequestLimitHandler abstractRequestLimitHandlerImpl = SpringConfig.getBean(requestLimitHandler);
                requestLimitHandlerCacheMap.put(requestLimitHandler, abstractRequestLimitHandlerImpl);
            }
            AbstractRequestLimitHandler abstractRequestLimitHandlerImpl = requestLimitHandlerCacheMap.get(requestLimitHandler);
            AbstractRequestLimitHandler.Operation operation = abstractRequestLimitHandlerImpl.handler(request, response);
            switch (operation) {
                case PROHIBIT:
                    return false;
                case SKIP:
                    return true;
                case ALLOW:
                    //do noting
                    break;
            }
        }
        return true;
    }

    /**
     * 检查请求限流
     *
     * @param limitModeKey     限流的key 限流模式+限流模式值+请求路径
     * @param maxRequestNumber 时间单位内允许的最大请求次数
     * @param timeUnit         时间单位
     * @param response         response
     * @return 是否禁止此次请求
     */
    private boolean requestLimitCheck(String limitModeKey, Integer maxRequestNumber, TimeUnit timeUnit, HttpServletResponse response) {
        if (redisService.hasKey(limitModeKey)) {
            Integer requestNumber = redisService.getCacheObject(limitModeKey);
            //过期的情况,即在限流时间段内没有超出最大请求,直接放行
            if (Objects.isNull(requestNumber)) {
                return false;
            }
            //超出了设置的最大请求次数
            if (requestNumber >= maxRequestNumber) {
                Result<Object> result = Result.create(null, HttpStatus.TOO_MANY_REQUESTS.value(), "┗|｀O′|┛ 你的请求太多了,稍等一会吧!");
                WebUtils.respondResult(response, result);
                return true;
            } else {
                long expire = redisService.getExpire(limitModeKey);
                if (expire > 0) {
                    //没有超出则次数则将次数加1后继续存放
                    redisService.setCacheObject(limitModeKey, ++requestNumber, expire, TimeUnit.SECONDS);
                }
            }
        } else {
            //第一次请求的情况,没有对应的key,进行初始化
            redisService.setCacheObject(limitModeKey, 1, 1L, timeUnit);

        }
        return false;
    }

    /**
     * 添加全局限流策略处理器
     *
     * @param handler RequestLimitHandler
     */
    public static void addGlobalRequestLimitHandler(AbstractRequestLimitHandler handler) {
        GLOBAL_REQUEST_LIMIT_HANDLERS.add(handler);
        GLOBAL_REQUEST_LIMIT_HANDLERS.sort(Comparator.comparing(AbstractRequestLimitHandler::getOrder));
    }


}
